﻿using System;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using dotSimpl.wlanner.Common;
using System.Text;

namespace dotSimpl.wlanner.Logic
{
    /// <summary>
    /// Represents serializable tree storage for settings.
    /// </summary>
    public class SettingsStorage
    {
        #region Internal Classes

        public class Node
        {
            public SerializableDictionary<string, Node> Child { get; set; }
            public string Value { get; set; }

            public Node(SerializableDictionary<string, Node> child, string value)
            {
                Child = child;
                Value = value;
            }

            public Node() { }
        }

        #endregion

        #region Fields

        private SerializableDictionary<string, Node> storage;
        XmlSerializer s = new XmlSerializer(typeof(SerializableDictionary<string, Node>));

        #endregion

        #region Methods

        /// <summary>
        /// Saves the storage to file.
        /// </summary>
        /// <param name="path">Path to file.</param>
        public void Save(string path)
        {
            TextWriter w = new StreamWriter(path);
            s.Serialize(w, storage);
            w.Close();
        }

        /// <summary>
        /// Loads the storage from file.
        /// </summary>
        /// <param name="path">Path to file.</param>
        public void Load(string path)
        {
            TextReader r = new StreamReader(path);
            storage = (SerializableDictionary<string, Node>)s.Deserialize(r);
            r.Close();
        }



        /// <summary>
        /// Stores value to specified path.
        /// </summary>
        /// <param name="path">Path to store value to.</param>
        /// <param name="value">Value to be stored,</param>
        /// <param name="overwrite">
        /// If true, existing data will be overwrited, 
        /// otherwise NotSupportedException will be thrown.
        /// </param>
        public void Store(string path, string value, bool overwrite)
        {
            if (PathExists(path) && !overwrite)
            {
                throw new NotSupportedException();
            }
            string[] parts = SplitPath(path);
            SerializableDictionary<string, Node> node = storage;

            for (int i = 0; i < parts.Length - 1; i++)
            {
                if (!node.ContainsKey(parts[i]))
                {
                    node.Add(parts[i], new Node(null, null));
                }
                if (node[parts[i]].Child == null)
                {
                    node[parts[i]].Child = new SerializableDictionary<string, Node>();
                }
                node = node[parts[i]].Child;
            }
            if (node.ContainsKey(parts.Last()))
            {
                node[parts.Last()].Value = value;
            }
            else
            {
                node.Add(parts.Last(), new Node(null, value));
            }
        }

        /// <summary>
        /// Gets value from path.
        /// </summary>
        /// <param name="path">Path to search value.</param>
        /// <returns>Value if found, null otherwise.</returns>
        public string Get(string path)
        {
            if (!PathExists(path))
            {
                return null;
            }

            string[] parts = SplitPath(path);
            SerializableDictionary<string, Node> node = storage;
            for (int i = 0; i < parts.Length - 1; i++)
            {
                node = node[parts[i]].Child;
            }

            return (string)node[parts.Last()].Value;
        }

        /// <summary>
        /// Deletes specified path. If path is not found, NotSupportedException will be thrown.
        /// </summary>
        /// <param name="path">Path to delete.</param>
        public void Delete(string path)
        {
            if (!PathExists(path))
            {
                return;
            }

            string[] parts = SplitPath(path);
            SerializableDictionary<string, Node> node = storage;
            for (int i = 0; i < parts.Length - 1; i++)
            {
                node = node[parts[i]].Child;
            }
            node.Remove(parts.Last());
        }

        /// <summary>
        /// Returns true, if path exists in storage, false otherwise.
        /// </summary>
        /// <param name="path">Path to test.</param>
        public bool PathExists(string path)
        {
            string[] parts = SplitPath(path);
            SerializableDictionary<string, Node> node = storage;
            for (int i = 0; i < parts.Length - 1; i++)
            {
                if (node.ContainsKey(parts[i]) && node[parts[i]].Child != null)
                {
                    node = node[parts[i]].Child;
                }
                else
                {
                    return false;
                }
            }
            return node.ContainsKey(parts.Last());
        }

        private static string[] SplitPath(string path)
        {
            return path.Split(PathSeparator);
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Creates path from components.
        /// </summary>
        public static string CreatePath(params string[] components)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < components.Length; i++)
            {
                if (i > 0)
                {
                    sb.Append(PathSeparator);
                }
                sb.Append(components[i]);
            }

            return sb.ToString();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets storage path separator.
        /// </summary>
        public static char PathSeparator
        {
            get { return Path.DirectorySeparatorChar; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new empty instance of SettingsStorage.
        /// </summary>
        public SettingsStorage()
        {
            storage = new SerializableDictionary<string, Node>();
        }

        /// <summary>
        /// Creates a new instance of SettinsStorage class and loads it from file.
        /// </summary>
        /// <param name="path">Path to storage saved file.</param>
        public SettingsStorage(string path)
        {
            Load(path);
        }

        #endregion
    }
}
